home *** CD-ROM | disk | FTP | other *** search
- (c) Copyright 1989-1999 Amiga, Inc. All rights reserved.
- The information contained herein is subject to change without notice, and
- is provided "as is" without warranty of any kind, either express or implied.
- The entire risk as to the use of this information is assumed by the user.
-
-
-
- Creating Multiple Processes With Re-entrant Code
-
- by Michael Sinz
-
- With the Amiga and its multitasking Exec, a whole new class of
- programming solutions is available for your applications. The
- Amiga allows you to divide up your program into multiple
- processes that do their work independently and asynchronously.
- For example, a spread-sheet program could have a background
- process which performs recalculation of the cells. This feature
- would allow the application to utilize the time between
- keystrokes to pre-calculate the value of the cell that is being
- worked or other cells -- with minimal code and none of the fancy
- tricks that would be required to accomplish the same thing on
- other systems.
-
- Of course, the Amiga's multitasking abilities also allow you to
- easily build "multi- window" type applications, too. This
- includes products such as word processors that let you edit more
- than one document at a time, spread-sheets that let you have
- more than one sheet open at a time, etc.
-
- For instance, a multi-window word processor can be written as a
- basic "single" document handler. By making the application
- re-entrant (i.e., no global or static variables) multiple
- documents can be opened by simply adding a new process for each
- context, or window, required. This would give your application
- the ability to start a document printing while the user works
- onsome other document in a different window.
-
- The example program listed below shows you how this can be done.
- The example will open a window with the standard system gadgets
- and a gadget that displays the task address. If you click on the
- gadget that displays the task address, another window will open
- with the same program running. This one is a new process but no
- new code has been loaded!
-
- To accomplish this feat, the program will check to see if there
- is a copy of itself already running whenever it is started. If
- there is, then the program signals the running code telling it to
- start the new copy, and then exits. In this way, multiple copies
- run using only one instance of the code.
-
- The program listing is rather long. To help you understand it,
- here is a list of all the functions in the program. There are
- eight in all:
-
- o MyOpenWindow() o Do_More()
- o MyCloseWindow() o MyCreateProc()
- o Do_Example() o StartMain()
- o myMain() o main()
-
- The program starts up a copy of itself with the function
- StartMain() and then waits for messages from the Do_More()
- function. If a message arrives from Do_More(), then either the
- user started another copy of the program from the CLI or the user
- clicked on the task address gadget. In either case, the function
- MyCreateProc() is called to restart the program without loading
- up any new code.
-
- The functions myMain(), Do_Example(), MyOpenWindow() and
- MyCloseWindow() just serve as a test case here - you could drop
- in code of your own instead. Don't forget, your code must be
- re-entrant. Variables which are not dynamically allocated at
- run-time are not allowed.
-
- The functions Do_More(), StartMain() and MyCreateProc() are the
- ones that do the multi-processing work. Do_More() sends the
- signal that the user wants to start the code another time.
- StartMain() waits for the signal and, if received, it calls the
- function MyCreateProc(). StartMain() is also responsible for
- keeping track of how many processes the user has started - it
- will not exit until all the processes are finished. The
- MyCreateProc() function actually does the work of beginning a new
- process using the same re-entrant code.
-
- Note that the program runs another copy of itself as a new
- process, not just as a task, so you can make calls to dos.library
- in your code. If the program only restarted itself as a
- task,then the using dos.library routines (such as printf() )
- would cause a system crash.
-
-
- /*
- * Example of how to start multiple processes from within the
- * same executable...
- *
- * This example assumes ANSI C and has been written and tested
- * with Lattice C 5.02 and 5.04...
- */
-
- /*
- * MakeFile used to make MultiProc...
- *
-
- #
- # MakeFile for MultiProc
- #
-
- CFLAGS= -b1 -cfirstq -ms0 -rr1 -v -w
-
- OBJS= MultiProc.o
-
- LIBS= LIB:lcsr.lib
-
- .c.o:
- @LC $(CFLAGS) $*
-
- MultiProc: $(OBJS)
- @BLink FROM LIB:c.o $(OBJS) TO MultiProc LIB $(LIBS) SMALLDATA SMALLCODE
-
- *
- *
- */
-
- #include <exec/types.h>
- #include <exec/memory.h>
- #include <intuition/intuition.h>
- #include <libraries/dos.h>
- #include <libraries/dosextens.h>
- #include <graphics/gfx.h>
- #include <graphics/regions.h>
- #include <graphics/rastport.h>
- #include <devices/timer.h>
-
- #include <proto/all.h>
-
- #include <string.h>
- #include <stdio.h>
-
- /* Lattice method of removing CTRL-C checking... */
- int CXBRK(VOID) { return(0); }
-
- /*
- * These library bases are shared...
- *
- * Note that you can not share the IEEE math libraries as they
- * need to set up some state information on a per-task basis.
- */
- extern struct IntuitionBase *IntuitionBase;
- extern struct GfxBase *GfxBase;
-
- /*
- * Structure that is a fake SegList for the code. It will contain
- * a single JMP instruction to the code we wish to start.
- */
- struct CodeHdr
- {
- ULONG SegSize;
- ULONG NextSeg;
- UWORD JumpInstr;
- APTR Function;
- };
-
- /*
- * Convert a normal pointer (CPTR or APTR) into a BCPL pointer (BPTR)
- */
- #define TO_BPTR(x) ((BPTR)(((ULONG)(x)) >> 2))
-
- /*
- * My message type for sending message between my processes and
- * the main process...
- */
- struct MyMSG
- {
- struct Message msg;
- BPTR lock; /* A directory lock for the new process */
- APTR UserData; /* Data that is passed to the process */
- SHORT type; /* My type recognition... */
- };
-
- #define MSG_TYPE_NEW 1
- #define MSG_TYPE_DIE 2
-
- /*
- * Constant task and port names... These should be unique for each
- * of your applications...
- */
- static char MyMessagePort[]="Sample Multi-Proc Port";
- static char MyProcessName[]="Sample Process Name";
-
- /*
- * This function will return TRUE if it was able to send a message
- * to start another task...
- *
- * The lock should be the default directory you wish the new process
- * to have. If it is NULL, it will get the starting directory...
- * The lock will be DupLock()ed and the copy will be sent to the
- * new process. So, the lock you pass to this function can be
- * UnLock()ed after you call it.
- *
- * UserData is a 32-bit value that can be whatever you define...
- */
- SHORT Do_More(BPTR lock,APTR UserData)
- {
- register struct MyMSG *msg;
- register struct MsgPort *port;
- register struct MsgPort *MyPort;
- register SHORT flag=FALSE;
-
- if (MyPort=CreatePort(NULL,NULL))
- {
- if (msg=AllocMem(sizeof(struct MyMSG),MEMF_PUBLIC|MEMF_CLEAR))
- {
- msg->msg.mn_ReplyPort=MyPort;
- msg->msg.mn_Length=sizeof(struct MyMSG);
- msg->UserData=UserData;
- msg->lock=lock;
- msg->type=MSG_TYPE_NEW;
-
- /*
- * Once we find the port, we don't want it to go away
- * so we prevent anyone else from running...
- *
- * Since this only happens during the starting of a process
- * this is a low overhead Forbid()
- */
- Forbid();
- if (port=FindPort(MyMessagePort)) /* Ok, send the message... */
- {
- PutMsg(port,(struct Message *)msg);
- WaitPort(MyPort);
- GetMsg(MyPort);
- flag=TRUE;
- }
- Permit();
- FreeMem(msg,(ULONG)(msg->msg.mn_Length));
- }
- DeletePort(MyPort);
- }
- return(flag);
- }
-
- /**********************************************************************/
-
- /*
- * Simple example program that opens a window with a gadget
- * If the gadget is pressed, it sends the signal to start another
- * copy of the program. If the close gadget is pressed, the window
- * shuts down.
- *
- * Note that any code that is to be multi-started from within the
- * the same code MUST be re-entrant. That means that global and
- * static values *MUST* be constants and not be changed...
- *
- * The text in the gadget displays the process ID (the process
- * structure pointer as found by FindTask(NULL)
- */
-
- #define MORE_GADGET 1
-
- /*
- * This is the stack size... Since you are a process and may do
- * AmigaDOS calls, the ABSOLUTE minimum would be around 2000.
- * A good recommended minimum is 4000...
- */
- #define EXAMPLE_STACK 4000L
-
- /*
- * This is the priority that the started code will run at...
- */
- #define EXAMPLE_PRI 0
-
- /*
- * Some global strings...
- */
- static char WindowTitle[10]="MultiProc";
-
- static char fontnam[11]="topaz.font";
- static struct TextAttr TOPAZ80={fontnam,TOPAZ_EIGHTY,0,0};
-
- struct MyGadget
- {
- struct Gadget Gadget;
- struct IntuiText IntuiText;
- struct Image Image;
- };
-
- /*
- * Note that to become reentrant we must allocate the gadget
- * structures rather than use global structures since gadgets
- * are data elements that are modified as they are used by
- * Intuition. Items such as the actual text strings and border
- * vectors and image data can be shared as they are read-only
- */
- struct Window *MyOpenWindow(char *MyID)
- {
- register struct Window *w=NULL;
- register struct MyGadget *gad;
- struct NewWindow nw;
-
- if (gad=AllocMem(sizeof(struct MyGadget),MEMF_PUBLIC|MEMF_CLEAR))
- {
- nw.LeftEdge=0;
- nw.TopEdge=22;
- nw.Width=200;
- nw.Height=50;
- nw.DetailPen=3;
- nw.BlockPen=1;
- nw.IDCMPFlags=CLOSEWINDOW|GADGETUP;
- nw.Flags=WINDOWCLOSE|WINDOWDRAG|WINDOWDEPTH|SIMPLE_REFRESH|NOCAREREFRESH;
- nw.FirstGadget=&(gad->Gadget);
- nw.CheckMark=NULL;
- nw.Title=WindowTitle;
- nw.Screen=NULL;
- nw.BitMap=NULL;
- nw.Type=WBENCHSCREEN;
-
- gad->Gadget.LeftEdge=4;
- gad->Gadget.TopEdge=11;
- gad->Gadget.Width=nw.Width-8;
- gad->Gadget.Height=nw.Height-13;
- gad->Gadget.Flags=GADGHCOMP|GADGIMAGE;
- gad->Gadget.Activation=RELVERIFY;
- gad->Gadget.GadgetType=BOOLGADGET;
- gad->Gadget.GadgetRender=(APTR)&(gad->Image);
- gad->Gadget.GadgetText=&(gad->IntuiText);
- gad->Gadget.GadgetID=MORE_GADGET;
-
- gad->IntuiText.TopEdge=(gad->Gadget.Height - 8) >> 1;
- gad->IntuiText.FrontPen=0;
- gad->IntuiText.DrawMode=JAM1;
- gad->IntuiText.ITextFont=&TOPAZ80;
- gad->IntuiText.IText=MyID;
- gad->IntuiText.LeftEdge=(gad->Gadget.Width-
- IntuiTextLength(&(gad->IntuiText))) >> 1;
-
- gad->Image.Width=gad->Gadget.Width;
- gad->Image.Height=gad->Gadget.Height;
- gad->Image.PlaneOnOff=3;
-
- /*
- * So, if the window opens, we use the UserData field to
- * keep track of the memory we allocated for the gadget
- * and other related items...
- */
- if (w=OpenWindow(&nw)) w->UserData=(UBYTE *)gad;
- else FreeMem(gad,sizeof(struct MyGadget));
- }
- return(w);
- }
-
- /*
- * We need to not only close the window but also free the memory
- * that we had allocated. Since the UserData field of the window
- * structure points to this data we just need to extract that pointer
- * so that we may free the memory after the window is closed...
- */
- VOID MyCloseWindow(struct Window *w)
- {
- register UBYTE *gad;
-
- if (w)
- {
- gad=w->UserData;
- CloseWindow(w);
- if (gad) FreeMem(gad,sizeof(struct MyGadget));
- }
- }
-
- /*
- * This is the example code
- *
- * The UserData that is passed comes from the startup message...
- *
- * Note that since this code could be running multiple times, it
- * must not count on ANY global variables other than CONSTANTS.
- * In other words, it must be re-entrant.
- */
- VOID Do_Example(APTR UserData)
- {
- register struct Window *w;
- register struct IntuiMessage *msg;
- register struct Gadget *gad;
- register SHORT flag=TRUE;
- register char *t;
- register char c;
- register ULONG task;
- char MyID[32];
-
- strcpy(MyID,"I am 0x00000000");
- task=(ULONG)FindTask(NULL);
- t=&(MyID[strlen(MyID)]);
- while (task) /* Generate the HEX string */
- { /* for the task's address. */
- c=(task & 15);
- task=task >> 4;
- *--t+=c + (c > 9 ? 7 : 0);
- }
-
- if (UserData) strcat(MyID," +");
-
- if (w=MyOpenWindow(MyID))
- {
- while (flag)
- {
- WaitPort(w->UserPort);
- while (msg=(struct IntuiMessage *)GetMsg(w->UserPort))
- {
- if (msg->Class==CLOSEWINDOW) flag=FALSE;
- else if (msg->Class==GADGETUP)
- {
- gad=(struct Gadget *)msg->IAddress;
- switch (gad->GadgetID)
- {
- case MORE_GADGET: Do_More(NULL,(APTR)1L);
- break;
- }
- }
- ReplyMsg((struct Message *)msg);
- }
- }
- MyCloseWindow(w);
- }
- }
-
- /**********************************************************************/
-
- /*
- * This is the font-end to the code that you have....
- *
- * When your code is called, your current directory will be
- * that of where the main code was called from.
- *
- * Note that __saveds is much like a GetA4()
- */
- static VOID __saveds myMain(VOID)
- {
- register struct MyMSG *msg;
- register struct MsgPort *port;
- register BPTR lock;
-
- /*
- * First, we need to get the startup-message...
- */
- port=&(((struct Process *)FindTask(NULL))->pr_MsgPort);
- WaitPort(port);
- msg=(struct MyMSG *)GetMsg(port);
-
- /*
- * Now, CD to the startup directory...
- */
- lock=CurrentDir(msg->lock);
-
- /*
- * Do your code here...
- * You are a process with a current directory but no CIS/COS
- * Since you are a process, you may do disk I/O
- *
- * We also pass the user data field from the startup message...
- */
- Do_Example(msg->UserData);
-
- /*
- * CD back to where we started...
- */
- CurrentDir(lock);
-
- /*
- * Now to signal that we are done. This lets the main task
- * know that we have finished and he may exit. The main task
- * keeps track of how many tasks are running and when the last
- * one exits, he may then exit...
- *
- * If we can't get this memory, we are in BIG trouble...
- * If we can't get this memory no one will know we are done...
- */
- Forbid();
- ReplyMsg((struct Message *)msg);
-
- /*
- * We don't Permit() here since we wish to drop out of this
- * task berfore the message gets delivered since the message
- * tells the main task that we are done...
- */
-
- /*
- * Since this task then is gone, the Forbid() is thus no
- * longer in effect...
- */
- }
-
- /*
- * This is my little process start code that sends a message to
- * the process if it got started...
- */
- static struct MsgPort *MyCreateProc(struct MsgPort *MyPort,BPTR seglist,BPTR lock,APTR UserData)
- {
- register struct MsgPort *proc=NULL;
- register struct MyMSG *msg;
-
- if (msg=AllocMem(sizeof(struct MyMSG),MEMF_PUBLIC|MEMF_CLEAR))
- {
- msg->msg.mn_ReplyPort=MyPort;
- msg->msg.mn_Length=sizeof(struct MyMSG);
- msg->type=MSG_TYPE_DIE;
- msg->UserData=UserData;
-
- if (proc=CreateProc(MyProcessName,EXAMPLE_PRI,seglist,EXAMPLE_STACK))
- {
- /*
- * Now that the process is running, we need to make a
- * copy of the lock and send the message...
- */
- msg->lock=DupLock(lock);
- PutMsg(proc,(struct Message *)msg);
- }
- else
- {
- /*
- * Since there was no process, we need to deallocate
- * the message we were goin to send...
- */
- FreeMem(msg,(ULONG)(msg->msg.mn_Length));
- }
- }
- return(proc);
- }
-
- /*
- * The reason argc is being passed is to give us an indication of
- * if we are a CLI-started program. For this example, we will
- * display different state information if started from a CLI...
- *
- * Note that the "if (argc) printf(..." statements are just for
- * this example and normally you would not do these...
- */
-
- #define EXAMPLE 1
-
- static VOID StartMain(int argc)
- {
- register struct MsgPort *port;
- register struct CodeHdr *p;
- register struct MyMSG *msg;
- register BPTR b_pointer;
- register BPTR StartingLock;
- register SHORT count=1;
-
- /*
- * Get a copy of the current directory lock...
- *
- * This lock will be passed to the first process that
- * starts...
- */
- b_pointer=CurrentDir(NULL);
- StartingLock=DupLock(b_pointer);
- CurrentDir(b_pointer);
-
- /*
- * Allocate the fake seglist. Since it must be longword
- * aligned, it can not be static in the code nor can it be
- * allocated on the stack (without assembly code, that is)
- */
- if (p=AllocMem(sizeof(struct CodeHdr),MEMF_PUBLIC|MEMF_CLEAR))
- {
- /*
- * 0x4EF9 is the 680x0 JMP instruction...
- */
- p->SegSize=sizeof(struct CodeHdr);
- p->JumpInstr=0x4EF9;
- p->Function=(APTR)myMain; /* The code to start... */
-
- /*
- * We need a BPTR to the NextSeg field of the SegList
- * for CreateProc()... So, this is what we make...
- */
- b_pointer=TO_BPTR(&p->NextSeg);
-
- /*
- * Ok, now for some critical sections...
- * We need to check if we are running somewhere else.
- * When doing this check, we need to make sure that someone
- * does not slip in at that time so the whole thing is done
- * in a Forbid()
- */
- Forbid();
- if (Do_More(StartingLock,NULL))
- {
-
- #ifdef EXAMPLE
- Permit();
- if (argc) printf("Found another MultiProc running...\n");
- Forbid();
- #endif EXAMPLE
-
- }
- else if (port=CreatePort(MyMessagePort,NULL))
- {
- if (MyCreateProc(port,b_pointer,StartingLock,NULL))
- {
- while (count)
- {
- /*
- * We now are safe from intervention...
- * So, we let the system run...
- */
- Permit();
-
- #ifdef EXAMPLE
- if (argc) printf("Now at %d processes...\n\nWaiting for message...\n",count);
- #endif EXAMPLE
-
- WaitPort(port);
-
- /*
- * In the following section of code, we need to balance
- * the Permit()/Forbid() pairs... This Forbid() is for
- * just this purpose. As you can see, it is imediate
- * Permit() if needed...
- */
- Forbid();
-
- while (msg=(struct MyMGS *)GetMsg(port))
- {
- /*
- * Ok, we are in now, and have our public port
- * and we know we are the only ones with it so
- * we now will let the system run...
- */
- Permit();
-
- switch (msg->type)
- {
- case MSG_TYPE_NEW:
- /*
- * Check if we were given a starting lock. If not,
- * we set it to the same as the first starting lock...
- */
- if (!(msg->lock)) msg->lock=StartingLock;
-
- /*
- * Try to start a process with the values
- * passed in the starting message...
- */
- if (MyCreateProc(port,b_pointer,msg->lock,msg->UserData))
- {
- /*
- * Count the fact that the process has
- * started...
- */
- count++;
- }
- /*
- * Reply the message to the person
- * asking to create this...
- */
- ReplyMsg((struct Message *)msg);
-
- #ifdef EXAMPLE
- if (argc) printf("\nStarted a process:\t");
- #endif EXAMPLE
-
- break;
-
- case MSG_TYPE_DIE:
- /*
- * Free the directory lock that we
- * gave the to the process that just
- * died...
- */
- if (msg->lock) UnLock(msg->lock);
-
- /*
- * Count the fact that another process
- * has died...
- */
- count--;
-
- /*
- * Since we sent this message, we will
- * have to deallocate it...
- */
- FreeMem(msg,(ULONG)(msg->msg.mn_Length));
-
- #ifdef EXAMPLE
- if (argc) printf("\nA process ended:\t");
- #endif EXAMPLE
-
- break;
- }
-
- /*
- * At this point, we don't want anyone to send a message
- * to us. This is because we may be in the middle of
- * cleaning up and going away. So, with this Forbid()
- * we prevent any new messages until after we check for
- * them and also check to see if we are about to exit.
- * If we do exit, the message port is deleted before
- * the next Permit() and thus we are safe from any
- * stray messages...
- */
- Forbid();
- }
- }
- }
- DeletePort(port);
-
- #ifdef EXAMPLE
- Permit();
- if (argc) printf("Exiting...\n");
- Forbid();
- #endif EXAMPLE
-
- }
- /*
- * Now that we have either started and exited or sent the message to
- * the other running MultiProc, we may let others back in...
- */
- Permit();
-
- FreeMem(p,p->SegSize);
- }
-
- if (StartingLock) UnLock(StartingLock);
- }
-
- /*
- * The main code should do whatever is needed to initialize the few
- * global parameters... For this example, we don't even need GfxBase
- * but it is here to just keep us company...
- */
- VOID main(int argc,char *argv[])
- {
- /*
- * Ok, so we need to startup... Now open the libraries that
- * everyone is going to use... (Note: Not the IEEE math
- * libraries. See above.)
- *
- * Also, setup the copy of the current directory lock
- */
- if (IntuitionBase=(struct IntuitionBase *)OpenLibrary("intuition.library",33L))
- {
- if (GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",33L))
- {
- /*
- * This is the main code that starts the multiple tasks
- */
- StartMain(argc);
-
- /*
- * Now clean up...
- */
- CloseLibrary((struct Library *)GfxBase);
- }
- CloseLibrary((struct Library *)IntuitionBase);
- }
- }
-
-